/*
 * Copyright (C) 2018 CyberAgent, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jp.co.cyberagent.android.gpuvideo;

import android.content.Context;
import android.content.res.TypedArray;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;

import jp.co.cyberagent.android.gpuimage.GLTextureView;
import jp.co.cyberagent.android.gpuvideo.R;
import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter;
import jp.co.cyberagent.android.gpuimage.util.Rotation;

import static jp.co.cyberagent.android.gpuvideo.GPUVideo.SURFACE_TYPE_SURFACE_VIEW;
import static jp.co.cyberagent.android.gpuvideo.GPUVideo.SURFACE_TYPE_TEXTURE_VIEW;

/**
 * @author wenjie
 */
abstract class GPUVideoView extends FrameLayout {

    private int surfaceType = SURFACE_TYPE_SURFACE_VIEW;
    protected View surfaceView;
    protected GPUVideo gpuVideo;
    private GPUImageFilter filter;
    private float ratio = 0.0f;

    public final static int RENDERMODE_WHEN_DIRTY = 0;
    public final static int RENDERMODE_CONTINUOUSLY = 1;

    public GPUVideoView(Context context) {
        super(context);
        init(context, null);
    }

    public GPUVideoView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.GPUImageView, 0, 0);
            try {
                surfaceType = a.getInt(R.styleable.GPUImageView_gpuimage_surface_type, surfaceType);
                onInit(a);
            } finally {
                a.recycle();
            }
        }
        gpuVideo = onCreateGPUVideo(context);
        if (surfaceType == SURFACE_TYPE_TEXTURE_VIEW) {
            surfaceView = onCreateGLTextureView(context, attrs);
            gpuVideo.setGLTextureView((GLTextureView) surfaceView);
        } else {
            surfaceView = onCreateGLSurfaceView(context, attrs);
            gpuVideo.setGLSurfaceView((GLSurfaceView) surfaceView);
        }
        addView(surfaceView);
    }

    protected GPUVideo onCreateGPUVideo(Context context) {
        return new GPUVideo(context);
    }

    protected void onInit(TypedArray a) {

    }

    @NonNull
    protected GLSurfaceView onCreateGLSurfaceView(Context context, AttributeSet attrs) {
        return new GLSurfaceView(context, attrs);
    }

    @NonNull
    protected GLTextureView onCreateGLTextureView(Context context, AttributeSet attrs) {
        return new GLTextureView(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (ratio != 0) {
            int width = MeasureSpec.getSize(widthMeasureSpec);
            int height = MeasureSpec.getSize(heightMeasureSpec);

            int newHeight;
            int newWidth;
            if (width / ratio < height) {
                newWidth = width;
                newHeight = Math.round(width / ratio);
            } else {
                newHeight = height;
                newWidth = Math.round(height * ratio);
            }

            int newWidthSpec = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY);
            int newHeightSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY);
            super.onMeasure(newWidthSpec, newHeightSpec);
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    /**
     * Retrieve the GPUImage instance used by this view.
     *
     * @return used GPUImage instance
     */
    public GPUVideo getGPUVideo() {
        return gpuVideo;
    }

    /**
     * Sets the background color
     *
     * @param red   red color value
     * @param green green color value
     * @param blue  red color value
     */
    public void setBackgroundColor(float red, float green, float blue) {
        gpuVideo.setBackgroundColor(red, green, blue);
    }

    /**
     * Set the rendering mode. When renderMode is
     * RENDERMODE_CONTINUOUSLY, the renderer is called
     * repeatedly to re-render the scene. When renderMode
     * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface
     * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY.
     *
     * @param renderMode one of the RENDERMODE_X constants
     * @see #RENDERMODE_CONTINUOUSLY
     * @see #RENDERMODE_WHEN_DIRTY
     * @see GLSurfaceView#setRenderMode(int)
     * @see GLTextureView#setRenderMode(int)
     */
    public void setRenderMode(int renderMode) {
        if (surfaceView instanceof GLSurfaceView) {
            ((GLSurfaceView) surfaceView).setRenderMode(renderMode);
        } else if (surfaceView instanceof GLTextureView) {
            ((GLTextureView) surfaceView).setRenderMode(renderMode);
        }
    }

    // TODO Should be an xml attribute. But then GPUImage can not be distributed as .jar anymore.
    public void setRatio(float ratio) {
        this.ratio = ratio;
        surfaceView.requestLayout();
        gpuVideo.deleteImage();
    }

    /**
     * Set the scale type of GPUImage.
     *
     * @param scaleType the new ScaleType
     */
    public void setScaleType(GPUVideo.ScaleType scaleType) {
        gpuVideo.setScaleType(scaleType);
    }

    /**
     * Sets the rotation of the displayed image.
     *
     * @param rotation new rotation
     */
    public void setRotation(Rotation rotation) {
        gpuVideo.setRotation(rotation);
        requestRender();
    }

    /**
     * Sets the rotation of the displayed image.
     *
     * @param rotation new rotation
     */
    public void setRotation(int rotation) {
        gpuVideo.setRotation(rotation);
        requestRender();
    }


    /**
     * Set the filter to be applied on the image.
     *
     * @param filter Filter that should be applied on the image.
     */
    public void setFilter(GPUImageFilter filter) {
        this.filter = filter;
        gpuVideo.setFilter(filter);
        requestRender();
    }

    /**
     * Get the current applied filter.
     *
     * @return the current filter
     */
    public GPUImageFilter getFilter() {
        return filter;
    }

    public void requestRender() {
        if (surfaceView instanceof GLSurfaceView) {
            ((GLSurfaceView) surfaceView).requestRender();
        } else if (surfaceView instanceof GLTextureView) {
            ((GLTextureView) surfaceView).requestRender();
        }
    }

    /**
     * Pauses the Surface.
     */
    public void onPause() {
        if (surfaceView instanceof GLSurfaceView) {
            ((GLSurfaceView) surfaceView).onPause();
        } else if (surfaceView instanceof GLTextureView) {
            ((GLTextureView) surfaceView).onPause();
        }
    }

    /**
     * Resumes the Surface.
     */
    public void onResume() {
        if (surfaceView instanceof GLSurfaceView) {
            ((GLSurfaceView) surfaceView).onResume();
        } else if (surfaceView instanceof GLTextureView) {
            ((GLTextureView) surfaceView).onResume();
        }
    }

    public void release() {
        if (gpuVideo != null) {
            gpuVideo.release();
        }
    }
}
